package firewing.service.adwords;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.xml.rpc.ServiceException;

import com.google.api.adwords.lib.AdWordsService;
import com.google.api.adwords.lib.AdWordsServiceLogger;
import com.google.api.adwords.lib.AdWordsUser;
import com.google.api.adwords.v201008.cm.AdGroup;
import com.google.api.adwords.v201008.cm.AdGroupAd;
import com.google.api.adwords.v201008.cm.AdGroupAdOperation;
import com.google.api.adwords.v201008.cm.AdGroupAdReturnValue;
import com.google.api.adwords.v201008.cm.AdGroupAdServiceInterface;
import com.google.api.adwords.v201008.cm.AdGroupCriterion;
import com.google.api.adwords.v201008.cm.AdGroupCriterionOperation;
import com.google.api.adwords.v201008.cm.AdGroupCriterionReturnValue;
import com.google.api.adwords.v201008.cm.AdGroupCriterionServiceInterface;
import com.google.api.adwords.v201008.cm.AdGroupOperation;
import com.google.api.adwords.v201008.cm.AdGroupReturnValue;
import com.google.api.adwords.v201008.cm.AdGroupServiceInterface;
import com.google.api.adwords.v201008.cm.AdGroupStatus;
import com.google.api.adwords.v201008.cm.BiddableAdGroupCriterion;
import com.google.api.adwords.v201008.cm.Keyword;
import com.google.api.adwords.v201008.cm.KeywordMatchType;
import com.google.api.adwords.v201008.cm.LanguageTarget;
import com.google.api.adwords.v201008.cm.Money;
import com.google.api.adwords.v201008.cm.Operator;
import com.google.api.adwords.v201008.cm.Target;
import com.google.api.adwords.v201008.cm.TextAd;
import com.google.api.adwords.v201008.o.AdGroupEstimateRequest;
import com.google.api.adwords.v201008.o.CampaignEstimateRequest;
import com.google.api.adwords.v201008.o.KeywordEstimate;
import com.google.api.adwords.v201008.o.KeywordEstimateRequest;
import com.google.api.adwords.v201008.o.TrafficEstimatorResult;
import com.google.api.adwords.v201008.o.TrafficEstimatorSelector;
import com.google.api.adwords.v201008.o.TrafficEstimatorServiceInterface;

import firewing.object.Trend;
import firewing.util.RandomTokenGenerator;

public class AdwordsAPI {

	private AdWordsUser user;

	private static final Logger log = Logger.getLogger(AdwordsAPI.class
			.getName());

	private final String CAMPAIGN_NAME = "AHT";

	private final long CAMPAIGN_ID = 69201222l;

	private final String ADGROUP_PREFIX = "AHT_AG_";

	private final String USER_EMAIL = "ads@yuan-shuai.info";

	private final String USER_PASS = "Vmonkey1!";

	private final String ADS_ACCOUNT = "public@yuan-shuai.info";

	private final String AFFLIATION = "UCL Computational Advertising";

	private final String DEV_TOKEN = "ReHBgQxc0XAxU_7yU6Jc_g";

	private final RandomTokenGenerator tokenGenerator = new RandomTokenGenerator();

	public void init() {
		try {
			log.info("Started initialising AdwordsService.");

			// Log SOAP XML request and response.
			// TODO this will override log4j configuration
			AdWordsServiceLogger.log();

			user = new AdWordsUser(USER_EMAIL, USER_PASS, ADS_ACCOUNT,
					AFFLIATION, DEV_TOKEN);

			log.info("Finished initialising AdwordsService.");
		} catch (Exception e) {
			log.severe("Failed to initialise AdwordsService.");
		}
	}

	public long addAdgroup() {
		// generate a new group name
		String groupName = ADGROUP_PREFIX + tokenGenerator.nextToken();

		long groupId = 0l;

		log.info("Start adding adgroup: " + groupName);

		try {
			AdGroupServiceInterface adGroupService = user
					.getService(AdWordsService.V201008.ADGROUP_SERVICE);

			// Create ad group.
			AdGroup adGroup = new AdGroup();
			adGroup.setName(groupName);
			adGroup.setStatus(AdGroupStatus.ENABLED);
			adGroup.setCampaignName(CAMPAIGN_NAME);
			adGroup.setCampaignId(CAMPAIGN_ID);

			// Create ad group bid.
			// ManualCPCAdGroupBids adGroupBids = new ManualCPCAdGroupBids();
			// adGroupBids.setKeywordMaxCpc(new Bid(new Money("GBP",
			// 10000000L)));
			// adGroup.setBids(adGroupBids);

			// Create operations.
			AdGroupOperation operation = new AdGroupOperation();
			operation.setOperand(adGroup);
			operation.setOperator(Operator.ADD);

			AdGroupOperation[] operations = new AdGroupOperation[] { operation };

			// Add ad group.
			AdGroupReturnValue result = adGroupService.mutate(operations);

			// Display new ad groups.
			if (result != null && result.getValue() != null) {
				for (AdGroup adGroupResult : result.getValue()) {
					groupId = adGroupResult.getId();
					log.info("Finished adding adgroup: " + groupName
							+ " and ID: " + groupId);
				}
			} else {
				log.severe("Failed to add adgroup.");
			}
		} catch (Exception ex) {
			log.severe("Failed to add adgroup.");
		}

		return groupId;
	}

	private void addKeyword(long groupId, String kw) {
		log.info("Started adding keyword to adgroup.");

		try {
			AdGroupCriterionServiceInterface adGroupCriterionService = user
					.getService(AdWordsService.V201008.ADGROUP_CRITERION_SERVICE);

			// Create keyword.
			Keyword keyword = new Keyword();
			keyword.setText(kw);
			keyword.setMatchType(KeywordMatchType.EXACT);

			// Create biddable ad group criterion.
			BiddableAdGroupCriterion keywordBiddableAdGroupCriterion = new BiddableAdGroupCriterion();
			keywordBiddableAdGroupCriterion.setAdGroupId(groupId);
			keywordBiddableAdGroupCriterion.setCriterion(keyword);

			// Create operations.
			AdGroupCriterionOperation keywordAdGroupCriterionOperation = new AdGroupCriterionOperation();
			keywordAdGroupCriterionOperation
					.setOperand(keywordBiddableAdGroupCriterion);
			keywordAdGroupCriterionOperation.setOperator(Operator.ADD);

			AdGroupCriterionOperation placementAdGroupCriterionOperation = new AdGroupCriterionOperation();

			AdGroupCriterionOperation[] operations = new AdGroupCriterionOperation[] { keywordAdGroupCriterionOperation, };

			// Add ad group criteria.
			AdGroupCriterionReturnValue result = adGroupCriterionService
					.mutate(operations);

			// Display ad group criteria.
			if (result != null && result.getValue() != null) {
				for (AdGroupCriterion adGroupCriterionResult : result
						.getValue()) {
					log.info("Finished adding keyword to adgroup.");
				}
			} else {
				log.severe("Failed to add keywords.");
			}
		} catch (Exception ex) {
			log.severe("Failed to add keywords.");
		}
	}

	public void addAd(String headLine, String descLine1, String descLine2,
			String displayURL, String landingURL, String keyword) {

		// create a new adgroup
		long groupId = addAdgroup();

		// add the keyword to the adgroup
		addKeyword(groupId, keyword);

		// add the advertisment
		try {
			AdGroupAdServiceInterface adGroupAdService = user
					.getService(AdWordsService.V201008.ADGROUP_AD_SERVICE);

			// Create text ad.
			TextAd textAd = new TextAd();
			textAd.setHeadline(headLine);
			textAd.setDescription1(descLine1);
			textAd.setDescription2(descLine2);
			textAd.setDisplayUrl(displayURL);
			textAd.setUrl(landingURL);

			// locate the adgroup
			AdGroupAd textAdGroupAd = new AdGroupAd();
			textAdGroupAd.setAdGroupId(groupId);
			textAdGroupAd.setAd(textAd);

			// create operation
			AdGroupAdOperation textAdGroupAdOperation = new AdGroupAdOperation();
			textAdGroupAdOperation.setOperand(textAdGroupAd);
			textAdGroupAdOperation.setOperator(Operator.ADD);

			AdGroupAdOperation[] operations = new AdGroupAdOperation[] { textAdGroupAdOperation };

			AdGroupAdReturnValue result = adGroupAdService.mutate(operations);

			if (result != null && result.getValue() != null) {
				for (AdGroupAd adGroupAdResult : result.getValue()) {
					log.info("Finished adding ads with ID: "
							+ adGroupAdResult.getAd().getId());
				}
			} else {
				log.severe("Failed to add ads.");
			}
		} catch (ServiceException ex) {
			log.severe("Failed to add ads.");
		} catch (RemoteException ex) {
			log.severe("Failed to add ads.");
		}
	}

	public Trend getPrice(Trend trend) {
		log.info("Started getting price for: " + trend);

		Trend ret = new Trend(trend);

		if (user == null) {
			// not initialised
			log.severe("AdwordsService has not been initialised.");

			this.init();
		}

		try {
			// Get the TrafficEstimatorService.
			TrafficEstimatorServiceInterface trafficEstimatorService = user
					.getService(AdWordsService.V201008.TRAFFIC_ESTIMATOR_SERVICE);

			// Create a keyword estimate request
			List<KeywordEstimateRequest> keywordEstimateRequests = new ArrayList<KeywordEstimateRequest>();
			KeywordEstimateRequest keywordEstimateRequest = new KeywordEstimateRequest();
			keywordEstimateRequest.setKeyword(new Keyword(null, null, trend
					.getPhrase(), KeywordMatchType.EXACT));
			keywordEstimateRequests.add(keywordEstimateRequest);

			// Create ad group estimate requests.
			List<AdGroupEstimateRequest> adGroupEstimateRequests = new ArrayList<AdGroupEstimateRequest>();
			AdGroupEstimateRequest adGroupEstimateRequest = new AdGroupEstimateRequest();
			adGroupEstimateRequest
					.setKeywordEstimateRequests(keywordEstimateRequests
							.toArray(new KeywordEstimateRequest[] {}));
			adGroupEstimateRequest.setMaxCpc(new Money(null, 250000000L)); // max
																			// bid
																			// CPC,
																			// £250/day
			adGroupEstimateRequests.add(adGroupEstimateRequest);

			// Create campaign estimate requests.
			List<CampaignEstimateRequest> campaignEstimateRequests = new ArrayList<CampaignEstimateRequest>();
			CampaignEstimateRequest campaignEstimateRequest = new CampaignEstimateRequest();
			campaignEstimateRequest
					.setAdGroupEstimateRequests(adGroupEstimateRequests
							.toArray(new AdGroupEstimateRequest[] {}));
			campaignEstimateRequest.setTargets(new Target[] {
			// new CountryTarget(null, false, "UK"), // ignore the Geo data
					new LanguageTarget(null, "en") });
			campaignEstimateRequests.add(campaignEstimateRequest);

			// Create selector.
			TrafficEstimatorSelector selector = new TrafficEstimatorSelector();
			selector.setCampaignEstimateRequests(campaignEstimateRequests
					.toArray(new CampaignEstimateRequest[] {}));

			// Get traffic estimates.
			TrafficEstimatorResult result = trafficEstimatorService
					.get(selector);

			// Display traffic estimates.
			if (result != null && result.getCampaignEstimates() != null) {
				KeywordEstimate[] keywordEstimates = result
						.getCampaignEstimates()[0].getAdGroupEstimates()[0]
						.getKeywordEstimates();
				for (int i = 0; i < keywordEstimates.length; i++) {
					Keyword keyword = keywordEstimateRequests.get(i)
							.getKeyword();
					KeywordEstimate keywordEstimate = keywordEstimates[i];

					// Find the mean of the min and max values.
					double meanAverageCpc = (keywordEstimate.getMin()
							.getAverageCpc().getMicroAmount() + keywordEstimate
							.getMax().getAverageCpc().getMicroAmount()) / 2.0 / 1000000; // convert
																							// to
																							// £
					double meanAveragePosition = (keywordEstimate.getMin()
							.getAveragePosition() + keywordEstimate.getMax()
							.getAveragePosition()) / 2.0;
					double meanClicks = (keywordEstimate.getMin().getClicks() + keywordEstimate
							.getMax().getClicks()) / 2.0;
					double meanTotalCost = (keywordEstimate.getMin()
							.getTotalCost().getMicroAmount() + keywordEstimate
							.getMax().getTotalCost().getMicroAmount()) / 2.0 / 1000000; // convert
																						// to
																						// £
					ret.setAverageCPC(meanAverageCpc);
					ret.setBidPosition(meanAveragePosition);
					ret.setDailyClick(meanClicks);
					ret.setDailyCost(meanTotalCost);
				}

				log.info("Finished getting price for: " + trend.getPhrase());

				return ret;
			} else {
				log.warning("No price info is returned.");

				return null;
			}
		} catch (Exception e) {
			log.severe("Failed to get price info.");

			return null;
		}
	}
}
